package com.itcam.daijia.map.service.impl;

import com.itcam.daijia.common.constant.RedisConstant;
import com.itcam.daijia.common.constant.SystemConstant;
import com.itcam.daijia.common.result.Result;
import com.itcam.daijia.common.util.LocationUtil;
import com.itcam.daijia.driver.client.DriverInfoFeignClient;
import com.itcam.daijia.map.repository.OrderServiceLocationRepository;
import com.itcam.daijia.map.service.LocationService;
import com.itcam.daijia.model.entity.driver.DriverSet;
import com.itcam.daijia.model.entity.map.OrderServiceLocation;
import com.itcam.daijia.model.form.map.OrderServiceLocationForm;
import com.itcam.daijia.model.form.map.SearchNearByDriverForm;
import com.itcam.daijia.model.form.map.UpdateDriverLocationForm;
import com.itcam.daijia.model.form.map.UpdateOrderLocationForm;
import com.itcam.daijia.model.vo.map.NearByDriverVo;
import com.itcam.daijia.model.vo.map.OrderLocationVo;
import com.itcam.daijia.model.vo.map.OrderServiceLastLocationVo;
import com.itcam.daijia.order.client.OrderInfoFeignClient;
import lombok.extern.slf4j.Slf4j;
import org.bson.types.ObjectId;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.Sort;
import org.springframework.data.geo.*;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.redis.connection.RedisGeoCommands;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;

@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class LocationServiceImpl implements LocationService {

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private OrderInfoFeignClient orderInfoFeignClient;

    @Autowired
    private DriverInfoFeignClient driverInfoFeignClient;

    @Autowired
    private OrderServiceLocationRepository orderServiceLocationRepository;

    @Autowired
    private MongoTemplate mongoTemplate;


    /**
     * 开启接单服务：更新司机经纬度位置
     * @param updateDriverLocationForm
     * @return
     */
    @Override
    public Boolean updateDriverLocation(UpdateDriverLocationForm updateDriverLocationForm) {
        /*
            Redis GEO（redis3.2新增） 主要用于存储地理位置信息，并对存储的信息进行相关操作
            后续在这个项目中用于乘客下单时寻找5公里范围内开启接单服务的司机，通过GEO进行计算
         */
        // 创建一个Point对象，包含司机的经纬度信息
        Point point = new Point(updateDriverLocationForm.getLongitude().doubleValue(), updateDriverLocationForm.getLatitude().doubleValue());
        // 将司机的地理位置信息添加到Redis的GEO集合中，集合名为RedisConstant.DRIVER_GEO_LOCATION，键为司机ID
        redisTemplate.opsForGeo().add(RedisConstant.DRIVER_GEO_LOCATION, point, updateDriverLocationForm.getDriverId().toString());
        // 返回操作结果
        return true;
    }

    /**
     * 关闭接单服务，删除司机经纬度位置
     * @param driverId
     * @return
     */
    @Override
    public Boolean removeDriverLocation(Long driverId) {
        // 从Redis的GEO集合中删除指定司机ID的地理位置信息
        redisTemplate.opsForGeo().remove(RedisConstant.DRIVER_GEO_LOCATION, driverId.toString());
        // 返回操作结果
        return true;
    }

    /**
     * 搜索附件满足条件的司机
     * @param searchNearByDriverForm
     * @return
     */
    @Override
    public List<NearByDriverVo> searchNearByDriver(SearchNearByDriverForm searchNearByDriverForm) {
        // 搜索经纬度位置5公里以内的司机
        // 定义经纬度点
        Point point = new Point(searchNearByDriverForm.getLongitude().doubleValue(), searchNearByDriverForm.getLatitude().doubleValue());
        // 定义距离：5公里（系统配置）
        Distance distance = new Distance(SystemConstant.NEARBY_DRIVER_RADIUS, RedisGeoCommands.DistanceUnit.KILOMETERS);
        // 定义以point点位中心，distance为距离的一个范围
        Circle circle = new Circle(point, distance);
        // 定义GEO参数
        RedisGeoCommands.GeoRadiusCommandArgs args = RedisGeoCommands.GeoRadiusCommandArgs
                .newGeoRadiusArgs()
                .includeDistance()
                .includeCoordinates()
                .sortAscending();

        // 1.GEORADIUS获取附件距离内的信息
        GeoResults<RedisGeoCommands.GeoLocation<String>> result = this.redisTemplate.opsForGeo().radius(RedisConstant.DRIVER_GEO_LOCATION, circle, args);

        // 2.收集信息，存入list
        assert result != null;
        List<GeoResult<RedisGeoCommands.GeoLocation<String>>> content = result.getContent();

        // 3.返回计算后的信息
        ArrayList<NearByDriverVo> list = new ArrayList<>();
        if (!CollectionUtils.isEmpty(content)) {
            Iterator<GeoResult<RedisGeoCommands.GeoLocation<String>>> iterator = content.iterator();
            while (iterator.hasNext()) {
                GeoResult<RedisGeoCommands.GeoLocation<String>> item = iterator.next();
                // 司机Id
                long driverId = Long.parseLong(item.getContent().getName());
                // 当前距离
                BigDecimal currentDistance = new BigDecimal(item.getDistance().getValue()).setScale(2, RoundingMode.HALF_UP);
                log.info("司机：{}，距离：{}", driverId, item.getDistance().getValue());

                // 获取司机接单设置参数
                DriverSet driverSet = driverInfoFeignClient.getDriverSet(driverId).getData();
                // 接单里程判断：acceptDistance==0,不限制
                if (driverSet.getAcceptDistance().doubleValue() != 0 && driverSet.getAcceptDistance().subtract(currentDistance).doubleValue() < 0) {
                    continue;
                }

                // 满足条件的附件司机信息
                NearByDriverVo nearByDriverVo = new NearByDriverVo();
                nearByDriverVo.setDriverId(driverId);
                nearByDriverVo.setDistance(currentDistance);
                list.add(nearByDriverVo);
            }
        }

        return list;

    }

    /**
     * 司机赶往代驾起始点：更新订单地址到缓存
     * @param updateOrderLocationForm
     * @return
     */
    @Override
    public Boolean updateOrderLocationToCache(UpdateOrderLocationForm updateOrderLocationForm) {
        OrderLocationVo orderLocationVo = new OrderLocationVo();
        orderLocationVo.setLongitude(updateOrderLocationForm.getLongitude());
        orderLocationVo.setLatitude(updateOrderLocationForm.getLatitude());
        redisTemplate.opsForValue().set(RedisConstant.UPDATE_ORDER_LOCATION + updateOrderLocationForm.getOrderId(), orderLocationVo);
        return true;
    }

    /**
     * 司机赶往代驾起始点：获取订单经纬度位置
     * @param orderId
     * @return
     */
    @Override
    public OrderLocationVo getCacheOrderLocation(Long orderId) {
        OrderLocationVo orderLocationVo = (OrderLocationVo) redisTemplate.opsForValue().get(RedisConstant.UPDATE_ORDER_LOCATION + orderId);
        return orderLocationVo;
    }

    /**
     * 开始代驾服务：保存代驾服务订单位置
     * @param orderLocationServiceFormList 订单服务位置表单列表
     * @return 是否保存成功
     */
    @Override
    public Boolean saveOrderServiceLocation(List<OrderServiceLocationForm> orderLocationServiceFormList) {
        // 创建一个OrderServiceLocation对象列表
        List<OrderServiceLocation> list = new ArrayList<>();
        // 遍历传入的订单服务位置表单列表
        orderLocationServiceFormList.forEach(item -> {
            // 创建一个OrderServiceLocation对象
            OrderServiceLocation orderServiceLocation = new OrderServiceLocation();
            // 将表单中的属性复制到OrderServiceLocation对象中
            BeanUtils.copyProperties(item, orderServiceLocation);
            // 设置OrderServiceLocation对象的id为一个新的ObjectId字符串
            orderServiceLocation.setId(ObjectId.get().toString());
            // 设置OrderServiceLocation对象的创建时间为当前时间
            orderServiceLocation.setCreateTime(new Date());
            // 将OrderServiceLocation对象添加到列表中
            list.add(orderServiceLocation);
        });
        // 将列表中的所有OrderServiceLocation对象保存到数据库中
        orderServiceLocationRepository.saveAll(list);
        // 返回true表示保存成功
        return true;
    }

    /**
     * 代驾服务：获取订单服务最后一个位置信息
     * @param orderId 订单ID
     * @return 订单服务最后一个位置信息
     */
    @Override
    public OrderServiceLastLocationVo getOrderServiceLastLocation(Long orderId) {
        // 创建一个Query对象
        Query query = new Query();
        // 添加查询条件，订单ID等于传入的orderId
        query.addCriteria(Criteria.where("orderId").is(orderId));
        // 按照创建时间降序排列
        query.with(Sort.by(Sort.Order.desc("createTime")));
        // 查询结果限制为1条
        query.limit(1);
        // 调用mongoTemplate的findOne方法，根据查询条件查询订单服务位置信息
        OrderServiceLocation orderServiceLocation = mongoTemplate.findOne(query, OrderServiceLocation.class);
        // 封装返回
        OrderServiceLastLocationVo orderServiceLastLocationVo = new OrderServiceLastLocationVo();
        // 将订单服务位置信息复制到订单服务最后一个位置信息对象中
        BeanUtils.copyProperties(orderServiceLocation, orderServiceLastLocationVo);
        // 返回订单服务最后一个位置信息
        return orderServiceLastLocationVo;
    }

    /**
     * 代驾服务：计算订单实际里程
     * @param orderId
     * @return
     */
    @Override
    public BigDecimal calculateOrderRealDistance(Long orderId) {
        // 根据订单ID查询订单服务位置信息，并按照创建时间升序排列
        List<OrderServiceLocation> orderServiceLocationList = orderServiceLocationRepository.findByOrderIdOrderByCreateTimeAsc(orderId);
        // 初始化实际距离为0
        double realDistance = 0;
        // 如果订单服务位置信息列表不为空
        if (!CollectionUtils.isEmpty(orderServiceLocationList)) {
            // 获取列表的大小
            int size = orderServiceLocationList.size() - 1;
            // 遍历列表，计算每个位置点与下一个位置点之间的距离，并累加到实际距离中
            for (int i = 0; i < size; i++) {
                OrderServiceLocation location1 = orderServiceLocationList.get(i);
                OrderServiceLocation location2 = orderServiceLocationList.get(i + 1);
                // 调用LocationUtil类的getDistance方法计算两个位置点之间的距离
                double distance = LocationUtil.getDistance(location1.getLatitude().doubleValue(),
                        location1.getLongitude().doubleValue(),
                        location2.getLatitude().doubleValue(),
                        location2.getLongitude().doubleValue());
                // 将计算出的距离累加到实际距离中
                realDistance += distance;
            }
        }
        // 测试过程中，没有真正代驾，实际代驾GPS位置没有变化，模拟：实际代价里程 = 预期里程 + 5
        if (realDistance == 0) {
            // 调用orderInfoFeignClient的getOrderInfo方法获取订单信息，并返回预期里程加5的结果
            return orderInfoFeignClient.getOrderInfo(orderId).getData().getExpectDistance().add(new BigDecimal("5"));
        }
        // 返回实际距离的BigDecimal表示
        return new BigDecimal(realDistance);
    }


}
